# NOTE: only tested in GNU/Linux.

## Binary dependencies.

CC = gcc
AR = ar


## Paths.

PREFIX ?= /usr/local
bindir ::= $(PREFIX)/bin
libdir ::= $(PREFIX)/lib
includedir = $(PREFIX)/include/lsup
MDB_DIR = ext/openldap/libraries/liblmdb
XXHASH_DIR = ext/xxHash
VALGRIND_DUMP = /tmp/lsup_valgrind.log
CALLGRIND_DUMP = /tmp/lsup_callgrind.out
MASSIF_DUMP = /tmp/lsup_massif.out

INCLUDE_BASE ::= . -Iinclude -I$(MDB_DIR) -I$(XXHASH_DIR) \
	-Iext/tpl/src -Iext/hashmap -Iext/log/src
INCLUDE ::= -I$(INCLUDE_BASE)
_CFLAGS ::= -Wall -fPIC -MMD -DLOG_USE_COLOR $(INCLUDE)
CFLAGS = $(_CFLAGS) -O3
DBG_CFLAGS = $(_CFLAGS) -Itest -O0 -g3 -DDEBUG
# NOTE: -luuid is a Linux system library. Other OS's might need a different
# link or a non-system library built.
LDFLAGS ::= -L. -L$(libdir) -llmdb -lxxhash -luuid

PARSER = bin/lemon
LEMON_SRC = ext/sqlite/tool/lemon.c
CODEC_DIR = src/codec

# External sources compiled in core object.
EXT_SRC ::= $(wildcard ext/log/src/*.c) \
	  	  $(wildcard ext/hashmap/*.c) \
	  	  $(wildcard ext/tpl/src/*.c)

# External headers of libraries compiled in core.
EXT_H ::= $(wildcard ext/log/src/*.h) \
	  	$(wildcard ext/tpl/src/*.h) \
	  	$(wildcard ext/hashmap/*.h)

LSUP_SRC = $(wildcard src/*.c)
SRC = $(EXT_SRC) $(LSUP_SRC)
TEST_SRC = $(wildcard test/*.c) test.c

EXT_OBJ ::= $(EXT_SRC:.c=.o)
# TODO This is extremely convoluted, simplify if possible.
CODEC_SRC ::= $(wildcard $(CODEC_DIR)/codec_*.c)
CODEC_REL_SRC ::= $(CODEC_SRC:$(CODEC_DIR)/%=%)
ALL_CODEC_REL_SRC ::= $(CODEC_REL_SRC) $(CODEC_REL_SRC:codec_%=parser_%) \
			$(CODEC_REL_SRC:codec_%=grammar_%)
CODEC_SRC = $(ALL_CODEC_REL_SRC:%=$(CODEC_DIR)/%)
CODEC_OBJ = $(CODEC_SRC:.c=.o)
CODEC_DBG_OBJ = $(CODEC_SRC:.c=_dbg.o)
OBJ = $(EXT_OBJ) $(LSUP_SRC:.c=.o)
DBG_OBJ = $(EXT_OBJ) $(LSUP_SRC:.c=_dbg.o)

DEPLIBS = libxxhash liblmdb
LIBS = liblsuprdf.a liblsuprdf.so
DBG_LIBS = liblsuprdf_dbg.a liblsuprdf_dbg.so

# For visual dep graph.
DEPS := $(shell echo "${INCLUDE_BASE}" | sed -e 's/ -I/,/g'),include/codec
DOCS = docs


## Environment.

# Tests need the freshly compiled libs.
export LD_LIBRARY_PATH = .:$(libdir)


## Rules.

.DEFAULT_GOAL := lib

# Extract all rule comments into a help message.
.PHONY: help
help:
	@echo "Command overview:"; echo; \
		grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
		| sed -n 's/^\(.*\): \(.*\)##\(.*\)/\1|\3/p' \
		| column -t  -s '|'
	

lib: $(DEPLIBS) codec $(LIBS) ## Compile main library (static and dynamic linking).


debug: $(DEPLIBS) codec_dbg $(DBG_LIBS) ## Compile main library with debug symbols.


# Static library.
liblsuprdf.a: $(OBJ)
	$(AR) rs $@ $^ $(CODEC_OBJ)


# Dynamic library.
liblsuprdf.so: $(OBJ)
	$(CC) -shared $(LDFLAGS) -o $@ $^ $(CODEC_OBJ)


# Static debug library.
liblsuprdf_dbg.a: $(DBG_OBJ)
	$(AR) rs $@ $^ $(CODEC_DBG_OBJ)


# Dynamic debug library.
liblsuprdf_dbg.so: $(DBG_OBJ)
	$(CC) -shared $(LDFLAGS) -o $@ $^ $(CODEC_DBG_OBJ)


# Debug objects.
%_dbg.o: %.c
	$(CC) $(DBG_CFLAGS) -c $^ -o $@


# Codecs in a subfolder.

.PHONY: codec
codec: $(PARSER)
	$(MAKE) -C $(CODEC_DIR) codec

.PHONY: codec_dbg
codec_dbg: $(PARSER)
	$(MAKE) -C $(CODEC_DIR) debug

# Build the parser executable.
$(PARSER): $(LEMON_SRC)
	$(CC) $^ -o $@


# Ext libraries.

.PHONY: libxxhash
libxxhash:
	$(MAKE) -C $(XXHASH_DIR)


.PHONY: liblmdb
liblmdb:
	$(MAKE) -C $(MDB_DIR)


install: lib ## Install library and dependencies to $PREFIX. May require sudo.
	@echo "Installing library files in $(PREFIX)."
	cd $(MDB_DIR); PREFIX=$(PREFIX) make install
	cd $(XXHASH_DIR); PREFIX=$(PREFIX) make install
	mkdir -p $(DESTDIR)$(libdir)
	mkdir -p $(DESTDIR)$(includedir)
	cp liblsuprdf.{a,so} $(DESTDIR)$(libdir) && \
		cp include/*.h $(EXT_H) $(DESTDIR)$(includedir)


debug_install: install debug ## Install standard and debug libraries.
	@echo "Installing debug library files in $(PREFIX)."
	cp liblsuprdf_dbg.{a,so} $(DESTDIR)$(libdir)
	

.PHONY: clean ## Clean up artifacts, including language parsers.
clean:
	rm -f src/*.[aod] ./*.[aod] src/codec/*.[aod] src/codec/*.out
	rm -rf build/*
	rm -f include/codec/grammar_*.h
	rm -f src/codec/{grammar,parser}_*.c


.PHONY: deepclean ## Clean up external libraries.
deepclean: clean
	cd $(MDB_DIR); make clean
	cd $(XXHASH_DIR); make clean


.PHONY: uninstall ## Uninstall library (not the dependencies).
uninstall:
	rm -f $(DESTDIR)$(libdir)/liblsuprdf*
	rm -rf $(DESTDIR)$(includedir)
	rm -f bin/test*


# For testing, use debug symbols.
bin/test: debug $(TEST_SRC)
	$(CC) $(DBG_CFLAGS) $(LDFLAGS) -llsuprdf_dbg \
		test.c -o bin/test


.PHONY: test
test: bin/test ## Run a test suite.
	@echo "Using libraries: "; ldd bin/test
	exec bin/test


.PHONY: gdb_test
gdb_test: bin/test ## Run a test suite within gdb.
	@echo "Using libraries: "; ldd bin/test
	exec gdb bin/test


lint:
	splint \
		$(INCLUDE) -Itest \
		-DUINT_MAX=0xFFFFFFFFUL \
		-nullpass \
		-posix-lib \
		test.c


.PHONY: memcheck
memcheck:
	valgrind \
	--leak-check=full --show-leak-kinds=all --track-origins=yes \
	--log-file=$(VALGRIND_DUMP) \
	./bin/test
	@echo "Memcheck complete. Valgrind log is at $(VALGRIND_DUMP)"


memtest: bin/test memcheck ## Run a test suite using Valgrind. Output to separate file.


# Performance test application. Essentially the profiling code without debug.
bin/profile: debug profile.c
	$(CC) $(CFLAGS) -g -DTESTING $(LDFLAGS) -llsuprdf_dbg \
		profile.c -o bin/profile


# Performance test application. Essentially the profiling code without debug.
bin/perftest: lib profile.c
	$(CC) $(CFLAGS) -g $(LDFLAGS) -llsuprdf profile.c -o bin/perftest


.PHONY: perftest
perftest: bin/perftest ## Run a performance test by creating, inserting and looking up triples.
	bin/perftest


.PHONY: profile
profile: bin/profile ## Run a profiling session. Output can be inspected with KCachegrind.
	LSUP_MDB_MAPSIZE=800000 valgrind --tool=callgrind \
		--callgrind-out-file="$(CALLGRIND_DUMP)" bin/perftest 1000
	@echo "Profile dump written at $(CALLGRIND_DUMP). Open it with "\
		"qcachegrind, kcachegrind, etc."


.PHONY: footprint
footprint: bin/perftest ## Measure memory footprint of storing 100K triples.
	LSUP_MDB_MAPSIZE=80000000 valgrind --tool=massif \
		--massif-out-file=$(MASSIF_DUMP) bin/perftest 100000
	@echo "Memory stats file written at $(MASSIF_DUMP). Open it with "\
		"massiftool or similar."


.PHONY: pytest
pytest: ## Run a test suite for the Python package.
	pip3 install --user .
	python3 test/cpython_test.py


# Requires cinclude2dot (https://www.flourish.org/cinclude2dot) and Graphviz.
depgraph: $(LSUP_SRC) $(CODEC_SRC) include/* include/codec/* ## Build a visual dependency graph of the code.
	cinclude2dot --merge=module --include=$(DEPS) \
		--exclude='test|ext' >| $(DOCS)/dev/deps.dot
	dot $(DOCS)/dev/deps.dot -Tpdf >| $(DOCS)/dev/deps.pdf
